package com.ruyuan.dfs.namenode.server;

import com.ruyuan.dfs.namenode.editslog.EditLogWrapper;
import com.ruyuan.dfs.namenode.editslog.EditslogInfo;
import com.ruyuan.dfs.namenode.fs.DiskNameSystem;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 抓取 EditLog 请求处理器
 *
 * @author Sun Dasheng
 */
@Slf4j
public class FetchEditLogBuffer {

    //最少10条记录
    public static final int BACKUP_NODE_FETCH_SIZE = 10;

    //存放 拉取 EditLog 缓存
    private List<EditLogWrapper> bufferedEditLog = new ArrayList<>();

    //磁盘组件
    private DiskNameSystem nameSystem;

    public FetchEditLogBuffer(DiskNameSystem nameSystem) {
        this.nameSystem = nameSystem;
    }

    /**
     * <pre>
     * 抓取 EditLog
     * 1. 判断缓冲区的 editsLog 数量没有达到阈值
     *      1.1 尝试从 editsLog 日志文件中读取 editsLog
     *      1.2 如果 editsLog 日志明显已经读取过了，直接将内存中最新的 editsLog 读取到缓存中
     *      1.3 如果 editsLog 日志文件没有读取过，只读取一部分 editsLog，直到达到 BackupNode 拉取 editsLog 条数的阈值即可
     * 2. 判断缓冲区的 editsLog 数量没有达到阈值
     *      2.1 如果没有达到阈值，直接返回空
     *      2.2 达到阈值，将数据返回给 BackupNode
     * </pre>
     *
     * @return editLog结果
     */
    public List<EditLogWrapper> fetch(long txId) throws IOException {
        List<EditLogWrapper> result = new ArrayList<>();
        //如果 bufferedEditLog 缓存的数据条目不够
        if (bufferedEditLog.size() <= BACKUP_NODE_FETCH_SIZE) {
            //抓取磁盘中的 editLog 填充到 bufferedEditLog 缓存中
            //每次用到了 则进行添加
            fetchEditLogAppendBuffer(txId);
        }
        //如果 bufferedEditLog 缓存的数据条目 正好够了
        if (bufferedEditLog.size() >= BACKUP_NODE_FETCH_SIZE) {
            //直接遍历进行返回
            //为什么需要迭代器迭代呢？ 并发快速失败？能够将遍历序列的操作和序列底层相分离，可以删除
            Iterator<EditLogWrapper> iterator = bufferedEditLog.iterator();
            while (iterator.hasNext()) {
                EditLogWrapper next = iterator.next();
                result.add(next);
                //可以删除
                iterator.remove();
            }
        }
        log.info("BackUpNode 抓取 NameNode 的 EditLog 结果：{}", result);
        return result;
    }

    /**
     * 抓取 editLog 填充到 bufferedEditLog 缓存中
     */
    private void fetchEditLogAppendBuffer(long txId) throws IOException {
        // 首先通过 内存中的 "editLog.文件" 索引来缩小范围  获得相应已刷盘"文件" 【无锁】
        // 根据 txId=150  能找出 这些文件 1_100.editLog  101_200.editLog   201_300.editLog
        List<EditslogInfo> sortedEditLogsFiles = nameSystem.getEditLog().getSortedEditLogFiles(txId);
        // 索引为空说明目前没有数据进行刷盘 那就从 双缓存 中获取
        // 1. 数据都还在 双缓冲区中
        // 2. 没有数据产生
        if (sortedEditLogsFiles.isEmpty()) {
            // 把 editLog 缓存填充到 bufferedEditLog 缓存中【加锁】
            appendMemoryEditLogToBuffer(txId);
        } else {
            // 有数据了就走返回逻辑 不管多少数据 【无锁】
            //不要每次都从内存中获取数据
            long bufferedTxId = 0L;
            // 遍历已经刷盘的 "索引" 文件
            for (EditslogInfo each : sortedEditLogsFiles) {
                // 当前文件中存储的最大消息 ID
                bufferedTxId = each.getEnd();
                // 当前文件存储的数据还是太少
                if (bufferedTxId <= txId) {
                    // 检查下一个文件
                    continue;
                }
                // 获取这个 101_200.editLog  文件里的多条数据条目
                List<EditLogWrapper> editsLogs = nameSystem.getEditLog().readEditLogFromFile(each.getName());
                // 把整个文件中符合条件的条目保存到 bufferedEditLog 中
                appendInternal(txId, editsLogs);
                // 如果当前文件缓存到 editLog 中已经满足抓取的需求，则跳出循环
                //txId=150+10 = 160 < bufferedTxId = 200
                if (txId + BACKUP_NODE_FETCH_SIZE < bufferedTxId) {
                    //数据够了 直接返回
                    break;
                } else {
                    // 当前的文件中的数据条数少于10条 则继续拉取下一个文件
                }
            }
            // 表示读文件读不出更多的 editLog 了，此时需要从内存缓冲中读取
            if (bufferedTxId <= txId) {
                //说明从磁盘中获得的数据少 尝试从缓存中再获得
                appendMemoryEditLogToBuffer(txId);
            }
        }
    }

    /**
     * 抓取内存中的 editsLog 到缓存中
     */
    private void appendMemoryEditLogToBuffer(long minTxId) {
        //获取当前 editLogBuffer 缓存中的数据
        List<EditLogWrapper> currentEditLog = nameSystem.getEditLog().getCurrentEditLog();
        if (currentEditLog != null) {
            appendInternal(minTxId, currentEditLog);
        }
    }

    /**
     * 将 EditLog 添加到 bufferedEditLog 缓存中
     * 提供给 BackUpNode fetch
     *
     * @param minTxId     最小的 TxId
     * @param editLogList EditLog 列表
     */
    private void appendInternal(long minTxId, List<EditLogWrapper> editLogList) {
        for (EditLogWrapper editLog : editLogList) {
            long txId = editLog.getTxId();
            //如果 当前每一条的数据 ID 都大于 minTxId 的话 就进行保存
            if (txId > minTxId) {
                bufferedEditLog.add(editLog);
            }
        }
    }
}
